// subber.js
const zmq = require('zeromq')
  , sock = zmq.socket('sub'),
  cfg = require('../config.json'),
  pg = require('../db/db'),
  Client = require('bitcoin-core'),
  { logger } = require('./logger');
let client, cost = [];


setInterval(() => {
  if (client == undefined) {
    initClient();
  }
}, 1000)

try {
  sock.connect('tcp://' + cfg.zmq.ip + ':' + cfg.zmq.port);//'tcp://192.168.2.154:28332'
  sock.subscribe('rawblock');
  logger.info(`Subscriber connected to port ${cfg.zmq.port}`);
  sock.monitor()

  //reconnect
  sock.on('disconnect', () => {
    sock.connect('tcp://' + cfg.zmq.ip + ':' + cfg.zmq.port);//'tcp://192.168.2.154:28332'
    logger.error('disconnect :tcp://' + cfg.zmq.ip + ':' + cfg.zmq.port)
  })
  //listen
  sock.on('message', async (topic, message) => {
    cost.push(new Date().getTime());
    if (cost.length > 1) {
      logger.info(`received a message interval:, ${cost.pop() - cost.pop()} ms`)
    }

    logger.info('received a message related to:', topic.toString("utf8"), 'containing message:', message);
    if (client == undefined) {
      initClient();
    }
    // write DB 
    await blockToDB();

  });

} catch (error) {
  logger.error(`zmq connect error: ${error}`)
}



// init client 
let initClient = async () => {
  try {
    client = new Client(cfg.bitCfg);
    if (typeof client == 'undefined') {
      client = new Client(cfg.bitCfg);
    }
    logger.info('bitcoin client init success.');
  } catch (error) {
    logger.error("client init error===>", error);
  }
}
// writer transaction to db
async function insertTxs(blockInfo) {
  if (blockInfo.height != 0) {
    for (const txid of blockInfo.tx) {
      let txhash = await pg.any(`SELECT txid FROM bittransactions WHERE bittransactions."txid" = '${txid}'`);
      if (txhash.length == 0) {
        let rawtx = await client.getRawTransaction(txid, true).catch((error) => {
          logger.error('getRawTransaction method error:==>', error);
          pg.none(`INSERT INTO errtx(txid) VALUES ('${txid}');`);
          global.gc();
        });
        global.gc();
        await handle_tx_sql(rawtx);
        await handle_utxo_sql(rawtx);
        logger.info(`the current block: ${blockInfo.height} txid: ${txid} write database success.`);
      } else {
        logger.info(`the current block: ${blockInfo.height} txid: ${txid} is already exists in database.`);
      }
      global.gc();
    }
    global.gc();
  }
}
// writer block to db
async function insertBlock(blockInfo) {
  let hash = await pg.any(`SELECT hash FROM bitblock WHERE bitblock."hash" = '${blockInfo.hash}'`);
  if (hash.length == 0) {
    await pg.none(wirteBlocks(blockInfo));
    logger.info(`the current block : ${blockInfo.height} tx size : ${blockInfo.tx.length} write database success.`);
    global.gc();
  } else {
    logger.info(`the current block: ${blockInfo.height} blockHash: ${(hash[hash.length - 1]).hash} tx_size: ${blockInfo.tx.length} is already exists in database.`);
  }
}
// get block info
let blockToDB = async () => {
  try {
    // The hash of the block header from the most recent block on the best block chain
    let headerHash = await client.getBestBlockHash();
    let blockInfo = await client.getBlock(headerHash);
    global.gc();
    let start, end;
    start = new Date().getTime()
    // parallel handle
    Promise.all([insertBlock(blockInfo), insertTxs(blockInfo)])
      .then((res) => {
        end = new Date().getTime()
        logger.info(`parallel handle success,cost:${end - start} ms`);
      })
      .catch(error => logger.error("parallel handle error: ", error))
  } catch (error) {
    global.gc();
    logger.error("client init error===>", error);
  }
}

let wirteBlocks = (result) => {

  let sql = `INSERT INTO public.bitblock( hash, confirmations, strippedsize, weight, height, version, "versionHex", merkleroot, tx, "time", 
    mediantime, nonce, bits, difficulty, chainwork, previousblockhash, nextblockhash)
	VALUES ('${result.hash}', ${result.confirmations}, ${result.strippedsize}, ${result.weight}, ${result.height}, ${result.version}, '${result.versionHex}', '${result.merkleroot}',
			'${JSON.stringify(result.tx)}',${result.time},
			'${result.mediantime}', '${result.nonce}', '${result.bits}', ${result.difficulty}, '${result.chainwork}',
			'${result.previousblockhash == undefined ? "" : result.previousblockhash}', 
            '${result.nextblockhash == undefined ? "" : result.nextblockhash}');`;
  global.gc();
  return sql;
}

let handle_tx_sql = async (txs) => {
  let sql_filed = `INSERT INTO bittransactions(txid, hash, version, size, vsize, locktime, vin, vout, hex `;
  let sql_value = `VALUES ('${txs.txid}', '${txs.hash}', ${txs.version}, ${txs.size}, ${txs.vsize}, 
    ${txs.locktime}, '${JSON.stringify(txs.vin)}', '${JSON.stringify(txs.vout)}', '${txs.hex}'`;

  if (txs.blockhash !== undefined) {
    sql_filed += `, blockhash, confirmations, "time", blocktime)`;
    sql_value += `, '${txs.blockhash}' , '${txs.confirmations}' , '${txs.time}', '${txs.blocktime}')`;
  }else {
    sql_filed += `)`;
    sql_value += `)`;
  }
  await pg.none(sql_filed + sql_value);
  global.gc();
}

let handle_utxo_sql = async (txs) => {
  for (const element of txs.vout) {
    let sql = `INSERT INTO utxos(
            txid, value, n, asm, hex, "reqSigs", type, addresses, isused)
            VALUES ('${txs.txid}', '${element.value}',
            ${element.n}, '${element.scriptPubKey.asm}', 
                    '${element.scriptPubKey.hex}', '${element.scriptPubKey.reqSigs == undefined ? "" : element.scriptPubKey.reqSigs}', 
                    '${element.scriptPubKey.type}', '${element.scriptPubKey.addresses == undefined ? "" : element.scriptPubKey.addresses}', null);`;
    await pg.none(sql);
    global.gc();
  }
}

// getBitTransanctions by address
let getBitTransanctions = async (transactionHash) => {
  try {
    // getBitTransanctions by address
    return await pg.any(`SELECT * FROM bittransactions WHERE bittransactions."txid" = '${transactionHash}'`);
    // success

  } catch (error) {
    logger.error("getBitTransanctions error===>", error);
  }
}

module.exports = { getBitTransanctions }